package service

import (
	"github.com/cn-ygf/go-admin/internal/modular/system/dao"
	"github.com/cn-ygf/go-admin/internal/modular/system/model"
	"github.com/cn-ygf/go-admin/internal/modular/system/vo"
	"github.com/cn-ygf/go-admin/pkg/utils"
	"net/url"
)

var (
	MenuService = new(menuService)
)

// menuService 菜单管理
type menuService struct {
}

// GetByID 根据menuId查询
func (s menuService) GetByID(menuId int64) (*model.SysMenu, error) {
	return dao.SysMenu.Where(dao.SysMenu.MenuID.Eq(menuId)).First()
}

func (s menuService) GetList(menuName string, status string) ([]*vo.SysMenuEntity, error) {
	var (
		menus []*model.SysMenu
		err   error
	)
	m := dao.SysMenu
	w := dao.SysMenu.Where()
	if menuName != "" {
		w = w.Where(m.MenuName.Like("%" + menuName + "%"))
	}
	if status != "" {
		w = w.Where(m.Status.Eq(status))
	}
	menus, err = w.Order(m.ParentID, m.OrderNum).Find()
	if err != nil {
		return nil, err
	}
	newMenus := make([]*vo.SysMenuEntity, len(menus))
	for index, item := range menus {
		newMenus[index] = &vo.SysMenuEntity{
			MenuID:    item.MenuID,
			MenuName:  item.MenuName,
			ParentID:  item.ParentID,
			OrderNum:  item.OrderNum,
			Path:      item.Path,
			Component: item.Component,
			Query:     item.Query,
			IsFrame:   item.IsFrame,
			IsCache:   item.IsCache,
			MenuType:  item.MenuType,
			Visible:   item.Visible,
			Status:    item.Status,
			Perms:     item.Perms,
			Icon:      item.Icon,
			Remark:    item.Remark,
			CreatedAt: item.CreatedAt,
			UpdatedAt: item.UpdatedAt,
		}
	}

	return newMenus, nil
}

// Update 修改菜单
func (s menuService) Update(req *vo.EditMenuReq) error {
	u := dao.SysMenu
	_, err := u.Where(u.MenuID.Eq(req.MenuID)).Updates(map[string]any{
		u.MenuName.ColumnName().String():  req.MenuName,
		u.ParentID.ColumnName().String():  req.ParentID,
		u.OrderNum.ColumnName().String():  req.OrderNum,
		u.Path.ColumnName().String():      req.Path,
		u.Component.ColumnName().String(): req.Component,
		u.Query.ColumnName().String():     req.Query,
		u.IsFrame.ColumnName().String():   req.IsFrame,
		u.IsCache.ColumnName().String():   req.IsCache,
		u.MenuType.ColumnName().String():  req.MenuType,
		u.Visible.ColumnName().String():   req.Visible,
		u.Status.ColumnName().String():    req.Status,
		u.Perms.ColumnName().String():     req.Perms,
		u.Icon.ColumnName().String():      req.Icon,
		u.Remark.ColumnName().String():    req.Remark,
		u.UpdateBy.ColumnName().String():  req.UpdateBy,
	})
	if err != nil {
		systemLog.Errorln(err.Error())
		return ServerError
	}
	return nil
}

// Add 添加菜单
func (s menuService) Add(req *vo.AddMenuReq) error {
	u := dao.SysMenu
	newMenu := &model.SysMenu{
		MenuName:  req.MenuName,
		ParentID:  req.ParentID,
		OrderNum:  req.OrderNum,
		Path:      req.Path,
		Component: req.Component,
		Query:     req.Query,
		IsFrame:   req.IsFrame,
		IsCache:   req.IsCache,
		MenuType:  req.MenuType,
		Visible:   req.Visible,
		Status:    req.Status,
		Perms:     req.Perms,
		Icon:      req.Icon,
		CreateBy:  req.CreateBy,
		UpdateBy:  "",
		Remark:    req.Remark,
	}
	err := u.Create(newMenu)
	if err != nil {
		systemLog.Errorln(err.Error())
		return ServerError
	}
	return nil
}

// Delete 删除菜单
func (s menuService) Delete(menuIds []int64) error {
	_, err := dao.SysMenu.Where(dao.SysMenu.MenuID.In(menuIds...)).Delete()
	return err
}

func (s menuService) GetMenuTree() ([]*vo.SysMenuEx, error) {
	var (
		menus []*model.SysMenu
		err   error
	)
	menus, err = s.GetMenuTreeAll()
	if err != nil {
		return nil, err
	}
	newMenus := make([]*vo.SysMenuEx, len(menus))
	for index, item := range menus {
		newMenus[index] = &vo.SysMenuEx{
			MenuID:    item.MenuID,
			MenuName:  item.MenuName,
			ParentID:  item.ParentID,
			OrderNum:  item.OrderNum,
			Path:      item.Path,
			Component: item.Component,
			Query:     item.Query,
			IsFrame:   item.IsFrame,
			IsCache:   item.IsCache,
			MenuType:  item.MenuType,
			Visible:   item.Visible,
			Status:    item.Status,
			Perms:     item.Perms,
			Icon:      item.Icon,
			CreateBy:  item.CreateBy,
			UpdateBy:  item.UpdateBy,
			Remark:    item.Remark,
			CreatedAt: item.CreatedAt,
			UpdatedAt: item.UpdatedAt,
			Children:  nil,
		}
	}
	return s.getChildPerms(newMenus, 0), nil
}

func (s menuService) GetMenuTreeByUserId(userId int64) ([]*vo.SysMenuEx, error) {
	var (
		menus []*model.SysMenu
		err   error
	)
	if UserService.IsAdmin(userId) {
		menus, err = s.GetMenuTreeAll()
	} else {
		menus, err = s.GetMenuListByUserId(userId)
	}
	if err != nil {
		return nil, err
	}
	newMenus := make([]*vo.SysMenuEx, len(menus))
	for index, item := range menus {
		newMenus[index] = &vo.SysMenuEx{
			MenuID:    item.MenuID,
			MenuName:  item.MenuName,
			ParentID:  item.ParentID,
			OrderNum:  item.OrderNum,
			Path:      item.Path,
			Component: item.Component,
			Query:     item.Query,
			IsFrame:   item.IsFrame,
			IsCache:   item.IsCache,
			MenuType:  item.MenuType,
			Visible:   item.Visible,
			Status:    item.Status,
			Perms:     item.Perms,
			Icon:      item.Icon,
			CreateBy:  item.CreateBy,
			UpdateBy:  item.UpdateBy,
			Remark:    item.Remark,
			CreatedAt: item.CreatedAt,
			UpdatedAt: item.UpdatedAt,
			Children:  nil,
		}
	}
	return s.getChildPerms(newMenus, 0), nil
}

func (s menuService) GetMenuTreeAll() ([]*model.SysMenu, error) {
	m := dao.SysMenu
	return m.Where(m.MenuType.In("M", "C", "F")).
		Where(m.Status.Eq("0")).
		Order(m.ParentID, m.OrderNum).Find()
}

func (s menuService) GetMenuListByUserId(userId int64) ([]*model.SysMenu, error) {
	m := dao.SysMenu
	menuIds := make([]int64, 0)
	roleIds, err := UserService.GetUserRoleIdsByID(userId)
	if err != nil {
		systemLog.Errorln(err.Error())
		return nil, err
	}
	userMenuList, err := dao.SysRoleMenu.Where(dao.SysRoleMenu.RoleID.In(roleIds...)).Find()
	if err != nil {
		systemLog.Errorln(err.Error())
		return nil, err
	}
	for _, um := range userMenuList {
		menuIds = append(menuIds, um.MenuID)
	}

	return m.Where(m.MenuType.In("M", "C")).
		Where(m.MenuID.In(menuIds...)).
		Where(m.Status.Eq("0")).
		Order(m.ParentID, m.OrderNum).
		Find()
}

// routeNode 菜单结构
type routeNode struct {
	Name       string       `json:"name"`
	Path       string       `json:"path"`
	Hidden     bool         `json:"hidden"`
	Redirect   string       `json:"redirect"`
	Component  string       `json:"component"`
	AlwaysShow bool         `json:"alwaysShow"`
	Query      string       `json:"query"`
	Meta       *meta        `json:"meta"`
	Children   []*routeNode `json:"children"`
}

type meta struct {
	Title   string `json:"title"`
	Icon    string `json:"icon"`
	NoCache bool   `json:"noCache"`
	Link    string `json:"link"`
}

func (s menuService) BuildTree(menus []*vo.SysMenuEx) ([]*vo.MenuTree, error) {
	r := make([]*vo.MenuTree, 0)
	for _, menu := range menus {
		node := &vo.MenuTree{
			Id:       menu.MenuID,
			Label:    menu.MenuName,
			Children: nil,
		}
		childrenMenu := menu.Children
		if len(childrenMenu) > 0 {
			children, err := s.BuildTree(childrenMenu)
			if err == nil {
				node.Children = children
			}
		}
		r = append(r, node)
	}
	return r, nil
}

func (s menuService) BuildMenus(menus []*vo.SysMenuEx) ([]*routeNode, error) {
	r := make([]*routeNode, 0)
	for _, menu := range menus {
		if menu.MenuType == "F" {
			continue
		}
		node := &routeNode{
			Name:       s.getRouteName(menu),
			Path:       s.getRoutePath(menu),
			Hidden:     menu.Visible == "1",
			Redirect:   "",
			Component:  s.getComponent(menu),
			AlwaysShow: false,
			Query:      menu.Query,
			Meta: &meta{
				Title:   menu.MenuName,
				Icon:    menu.Icon,
				NoCache: menu.IsCache == 1,
				Link:    "",
			},
			Children: nil,
		}
		childrenMenu := menu.Children
		if len(childrenMenu) > 0 && menu.MenuType == "M" {
			node.AlwaysShow = true
			node.Redirect = "noRedirect"
			children, err := s.BuildMenus(childrenMenu)
			if err == nil {
				node.Children = children
			}
		} else if s.isMenuFrame(menu) {
			node.Meta = nil
			childrenList := make([]*routeNode, 0)
			link := ""
			if utils.IsHttp(menu.Path) {
				link = menu.Path
			}
			childrenList = append(childrenList, &routeNode{
				Name:       utils.Capitalize(menu.Path),
				Path:       menu.Path,
				Hidden:     false,
				Redirect:   "",
				Component:  menu.Component,
				AlwaysShow: false,
				Query:      menu.Query,
				Meta: &meta{
					Title:   menu.MenuName,
					Icon:    menu.Icon,
					NoCache: menu.IsCache == 1,
					Link:    link,
				},
				Children: nil,
			})
			node.Children = childrenList
		} else if menu.ParentID == 0 && s.isInnerLink(menu) {
			node.Meta = &meta{
				Title:   menu.MenuName,
				Icon:    menu.Icon,
				NoCache: false,
				Link:    "",
			}
			node.Path = "/"
			childrenList := make([]*routeNode, 0)
			path := utils.InnerLinkReplaceEach(menu.Path)
			childrenList = append(childrenList, &routeNode{
				Name:       utils.Capitalize(path),
				Path:       path,
				Hidden:     false,
				Redirect:   "",
				Component:  "InnerLink",
				AlwaysShow: false,
				Query:      menu.Query,
				Meta: &meta{
					Title: menu.MenuName,
					Icon:  menu.Icon,
					Link:  menu.Path,
				},
				Children: nil,
			})
			node.Children = childrenList
		}
		r = append(r, node)
	}
	return r, nil
}

// getChildPerms 根据父节点的ID获取所有子节点
func (s menuService) getChildPerms(menus []*vo.SysMenuEx, parentId int64) []*vo.SysMenuEx {
	reutrnList := make([]*vo.SysMenuEx, 0)
	for _, menu := range menus {
		if menu.ParentID == parentId {
			// 根菜单递归处理
			s.recursionFn(menus, menu)
			reutrnList = append(reutrnList, menu)
		}
	}
	return reutrnList
}

func (s menuService) recursionFn(menus []*vo.SysMenuEx, menu *vo.SysMenuEx) {
	childList := s.getChildList(menus, menu)
	menu.Children = childList
	for _, item := range childList {
		c := s.getChildList(menus, item)
		if len(c) > 0 {
			s.recursionFn(menus, item)
		}
	}
}

func (s menuService) getChildList(menus []*vo.SysMenuEx, menu *vo.SysMenuEx) []*vo.SysMenuEx {
	list := make([]*vo.SysMenuEx, 0)
	for _, item := range menus {
		if item.ParentID == menu.MenuID {
			list = append(list, item)
		}
	}
	return list
}

// getComponent 获取组件
func (s menuService) getComponent(menu *vo.SysMenuEx) string {
	component := "Layout"
	if menu.Component != "" && !s.isMenuFrame(menu) {
		component = menu.Component
	} else if menu.Component == "" && menu.ParentID != 0 && s.isInnerLink(menu) {
		component = "InnerLink"
	} else if menu.Component == "" && s.isParentView(menu) {
		component = "ParentView"
	}
	return component
}

// getRoutePath 获取路由path
func (s menuService) getRoutePath(menu *vo.SysMenuEx) string {
	routerPath := menu.Path
	// 内链打开外网方式
	if menu.ParentID != 0 && s.isInnerLink(menu) {
		routerPath = utils.InnerLinkReplaceEach(routerPath)
	}
	// 非外链并且是一级目录（类型为目录）
	if menu.ParentID == 0 && menu.MenuType == "M" && menu.IsFrame == 1 {
		routerPath = "/" + menu.Path
	} else if s.isMenuFrame(menu) {
		// 非外链并且是一级目录（类型为菜单）
		routerPath = "/"
	}

	return routerPath
}

// getRouteName 获取路由名称
func (s menuService) getRouteName(menu *vo.SysMenuEx) string {
	if s.isMenuFrame(menu) {
		return ""
	}
	return utils.Capitalize(menu.Path)
}

// isMenuFrame 是否为外链组件
func (s menuService) isMenuFrame(menu *vo.SysMenuEx) bool {
	return menu.ParentID == 0 && menu.MenuType == "C" && menu.IsFrame == 1
}

// isInnerLink 是否为内链组件
func (s menuService) isInnerLink(menu *vo.SysMenuEx) bool {
	u, err := url.Parse(menu.Path)
	if err != nil {
		return false
	}
	return menu.IsFrame == 1 && (u.Scheme == "http" || u.Scheme == "https")
}

// isParentView 是否为parent_view组件
func (s menuService) isParentView(menu *vo.SysMenuEx) bool {
	return menu.ParentID != 0 && menu.MenuType == "M"
}

// CheckMenuNameUnique 检查菜单名称是否已存在
func (s menuService) CheckMenuNameUnique(menuId int64, menuName string) bool {
	if menuId == 0 {
		count, err := dao.SysMenu.Where(dao.SysMenu.MenuName.Eq(menuName)).Count()
		if err != nil {
			systemLog.Errorln(err.Error())
			return true
		}
		return count < 1
	}
	menu, err := s.GetByID(menuId)
	if err != nil {
		systemLog.Errorln(err.Error())
		return true
	}
	if menu.MenuName == menuName {
		return true
	}
	count, err := dao.SysMenu.Where(dao.SysMenu.MenuName.Eq(menuName)).Count()
	if err != nil {
		systemLog.Errorln(err.Error())
		return true
	}
	return count < 1
}

// HasChildByMenuId 判断是否存在子菜单
func (s menuService) HasChildByMenuId(menuId int64) bool {
	l, err := dao.SysMenu.Where(dao.SysMenu.ParentID.Eq(menuId)).Count()
	if err != nil {
		systemLog.Errorln(err.Error())
		return true
	}
	return l > 0
}

// CheckMenuExistRole 判断是否存在角色关联
func (s menuService) CheckMenuExistRole(menuId int64) bool {
	l, err := dao.SysRoleMenu.Where(dao.SysRoleMenu.MenuID.Eq(menuId)).Count()
	if err != nil {
		systemLog.Errorln(err.Error())
		return true
	}
	return l > 0
}
